////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001-2005.
// -------------------------------------------------------------------------
//  File name:   LocalizedStringManager.h
//  Version:     v1.00
//  Created:     22/9/2005 by Timur.
//  Compilers:   Visual Studio.NET 2003
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "LocalizedStringManager.h"
#include <IInput.h>
#include <ISystem.h>
#include "System.h" // to access InitLocalization()
#include <CryPath.h>
#include <IConsole.h>
#include <StringUtils.h>
#include <locale.h>
#include <time.h>

#define MAX_CELL_COUNT 32

// CVAR names
const char c_sys_localization_debug[] = "sys_localization_debug";

enum ELocalizedXmlColumns
{
	ELOCALIZED_COLUMN_SKIP  = 0,
	ELOCALIZED_COLUMN_KEY, 
	ELOCALIZED_COLUMN_AUDIOFILE,
	ELOCALIZED_COLUMN_ORIGINAL_CHARACTER_NAME,
	ELOCALIZED_COLUMN_TRANSLATED_CHARACTER_NAME,
	ELOCALIZED_COLUMN_ORIGINAL_TEXT,
	ELOCALIZED_COLUMN_TRANSLATED_TEXT,
	ELOCALIZED_COLUMN_ORIGINAL_ACTOR_LINE,
	ELOCALIZED_COLUMN_TRANSLATED_ACTOR_LINE,
	ELOCALIZED_COLUMN_USE_SUBTITLE,
	ELOCALIZED_COLUMN_VOLUME,
	ELOCALIZED_COLUMN_SOUNDEVENT,
	ELOCALIZED_COLUMN_RADIO_RATIO,
	ELOCALIZED_COLUMN_EVENTPARAMETER,
	ELOCALIZED_COLUMN_SOUNDMOOD,
	ELOCALIZED_COLUMN_IS_DIRECT_RADIO,
	// legacy names
	ELOCALIZED_COLUMN_LEGACY_PERSON,
	ELOCALIZED_COLUMN_LEGACY_CHARACTERNAME,
	ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTERNAME,
	ELOCALIZED_COLUMN_LEGACY_ENGLISH_DIALOGUE,
	ELOCALIZED_COLUMN_LEGACY_TRANSLATION,
	ELOCALIZED_COLUMN_LEGACY_YOUR_TRANSLATION,
	ELOCALIZED_COLUMN_LEGACY_ENGLISH_SUBTITLE,
	ELOCALIZED_COLUMN_LEGACY_TRANSLATED_SUBTITLE,
	ELOCALIZED_COLUMN_LAST,
};

// The order must match to the order of the ELocalizedXmlColumns
static const char* sLocalizedColumnNames[] = 
{
	// everyhing read by the file will be convert to lower cases
	"skip",
	"key",
	"audio_filename",
	"original character name",
	"translated character name",
	"original text",
	"translated text",
	"original actor line",
	"translated actor line",
	"use subtitle",
	"volume",
	"prototype event",
	"radio ratio",
	"eventparameter",
	"soundmood",
	"is direct radio",
	// legacy names
	"person",
	"character name",
	"translated character name",
	"english dialogue",
	"translation",
	"your translation",
	"english subtitle",
	"translated subtitle",
	};

//Please ensure that this array matches the contents of EPlatformIndependentLanguageID in ILocalizationManager.h
static const char* PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ ILocalizationManager::ePILID_MAX_OR_INVALID ] =
	{
		"japanese",
		"english",
		"french",
		"spanish",
		"german",
		"italian",
		"dutch",
		"portuguese",
		"russian",
		"korean",
		"chineset",	// Traditional Chinese
		"chineses",	// Simplified Chinese
		"finnish",
		"swedish",
		"danish",
		"norwegian",
		"polish",
		"arabic",
		"czech",
		"turkish"
	};

//////////////////////////////////////////////////////////////////////////
static void ReloadDialogData( IConsoleCmdArgs* pArgs )
{
	//gEnv->pSystem->GetLocalizationManager()->FreeData();
	//CSystem *pSystem = (CSystem*) gEnv->pSystem;
	//pSystem->InitLocalization();
	//pSystem->OpenBasicPaks();
	gEnv->pSystem->GetLocalizationManager()->ReloadData();
}

//////////////////////////////////////////////////////////////////////////
static void TestFormatMessage (IConsoleCmdArgs* pArgs)
{
	string fmt1 ("abc %1 def % gh%2i %");
	string fmt2 ("abc %[action:abc] %2 def % gh%1i %1");
	wstring wfmt (L"abc %2 def % gh%1i %");
	string out1, out2;
	wstring wout;
	gEnv->pSystem->GetLocalizationManager()->FormatStringMessage(out1, fmt1, "first", "second", "third");
	CryLogAlways("%s", out1.c_str());
	gEnv->pSystem->GetLocalizationManager()->FormatStringMessage(out2, fmt2, "second");
	CryLogAlways("%s", out2.c_str());
	gEnv->pSystem->GetLocalizationManager()->FormatStringMessage(wout, wfmt, L"second", L"first");
}



//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
CLocalizedStringsManager::CLocalizedStringsManager( ISystem *pSystem )
: m_cvarLocalizationDebug(0)
, m_availableLocalizations(0)
{
	m_pSystem = pSystem;
	m_pSystem->GetISystemEventDispatcher()->RegisterListener(this);

	m_languages.reserve(4);
	m_tempWString.resize(1024); 
	m_tempWString[0] = L'\0';
	m_pLanguage = 0;

	REGISTER_COMMAND( "ReloadDialogData",ReloadDialogData,VF_NULL,
		"Reloads all localization dependent XML sheets for the currently set language.");

	REGISTER_COMMAND( "_TestFormatMessage", TestFormatMessage,VF_NULL,"" );

	REGISTER_CVAR2(c_sys_localization_debug, &m_cvarLocalizationDebug, m_cvarLocalizationDebug, VF_CHEAT,
		"Toggles debugging of the Localization Manager.\n"
		"Usage: sys_localization_debug [0..2]\n"
		"1: outputs warnings\n"
		"2: outputs extended information and warnings\n"
		"Default is 0 (off).");

	//Check that someone hasn't added a language ID without a language name
	assert( PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ ILocalizationManager::ePILID_MAX_OR_INVALID - 1 ] != 0 );
}

//////////////////////////////////////////////////////////////////////
CLocalizedStringsManager::~CLocalizedStringsManager()
{
	FreeData();
}

//////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FreeLocalizationData()
{
	for (uint32 i = 0; i < m_languages.size(); i++)
	{
		std::for_each(m_languages[i]->m_vLocalizedStrings.begin(),m_languages[i]->m_vLocalizedStrings.end(),stl::container_object_deleter());
		m_languages[i]->m_keysMap.clear();
		m_languages[i]->m_vLocalizedStrings.clear();
	}
	m_loadedTables.clear();
}

//////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FreeData()
{
	FreeLocalizationData();

	for (uint32 i = 0; i < m_languages.size(); i++)
	{
		delete m_languages[i];
	}
	m_languages.resize(0);
	m_loadedTables.clear();

	m_pLanguage = 0;
}

const char* CLocalizedStringsManager::LangNameFromPILID( const ILocalizationManager::EPlatformIndependentLanguageID id )
{
	assert( id >= 0 && id < ILocalizationManager::ePILID_MAX_OR_INVALID );
	return PLATFORM_INDEPENDENT_LANGUAGE_NAMES[ id ];
}

//Uses bitwise operations to compare the localizations we provide in this SKU and the languages that the platform supports.
//Returns !0 if we provide more localizations than are available as system languages (this is required for PS3 TRC)
ILocalizationManager::TLocalizatonBitfield CLocalizedStringsManager::MaskSystemLanguagesFromSupportedLocalizations( const ILocalizationManager::TLocalizatonBitfield systemLanguages )
{
	assert( m_availableLocalizations != 0 );	//Make sure to set the available localizations!
	return (~systemLanguages)&m_availableLocalizations;
}

//Returns !0 if the language is supported.
ILocalizationManager::TLocalizatonBitfield CLocalizedStringsManager::IsLanguageSupported( const ILocalizationManager::EPlatformIndependentLanguageID id )
{
	assert( m_availableLocalizations != 0 );	//Make sure to set the available localizations!
	return m_availableLocalizations & ( 1 << id );
}

void CLocalizedStringsManager::SetAvailableLocalizationsBitField( const ILocalizationManager::TLocalizatonBitfield availableLocalizations )
{
	m_availableLocalizations = availableLocalizations;
}

#define LANG_NAME_BUFFER_LEN 65
void CLocalizedStringsManager::SetAvailableLocalizationsCSVString( const char *sAvailableLocalizations )
{
	assert(sAvailableLocalizations);
	m_availableLocalizations = 0;
	const char* c = sAvailableLocalizations;
	char langNameBuffer[ LANG_NAME_BUFFER_LEN ];
	int len = 0, locale;
	bool done = false;

	for (; !done; c++)
	{
		if (isalnum( (int)(*c) ))
		{
			langNameBuffer[len] = *c;
			len++;
			if( len == LANG_NAME_BUFFER_LEN )
			{
				//Throw a fatal error, the CVAR is improperly formatted or includes a huge language name that we can't parse and this will break localization
				CryFatalError( "Language name in CSV string is too long; language name starting with: %s is longer than LANG_NAME_BUFFER_LEN characters", langNameBuffer );
			}
		}
		else
		{
			langNameBuffer[len] = '\0';
			for( locale = 0; locale < ILocalizationManager::ePILID_MAX_OR_INVALID; locale++ )
			{
				if( stricmp( langNameBuffer, PLATFORM_INDEPENDENT_LANGUAGE_NAMES[locale] ) == 0 )
				{
					m_availableLocalizations |= ( 1 << locale );
					break;
				}
			}
			if( locale == ILocalizationManager::ePILID_MAX_OR_INVALID )
			{
				CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"LocalizeStringManager encountered an unsupported language in setAvailableLocalizationsCSVString: %s", langNameBuffer );
			}
			len = 0;
			if (*c == '\0')
			{
				done = true;
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////
const char* CLocalizedStringsManager::GetLanguage()
{
	if (m_pLanguage == 0)
		return "";
	return m_pLanguage->sLanguage.c_str();
}

//////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::SetLanguage( const char *sLanguage )
{
	if (m_cvarLocalizationDebug >= 2)
		CryLog("<Localization> Set language to %s", sLanguage);

//#if !defined(EXCLUDE_SCALEFORM_SDK)
//	CryGFxTranslator::GetAccess().SetWordWrappingMode(sLanguage);
//#endif

	// Check if already language loaded.
	for (uint32 i = 0; i < m_languages.size(); i++)
	{
		if (stricmp(sLanguage,m_languages[i]->sLanguage)==0)
		{
			InternalSetCurrentLanguage(m_languages[i]);
			return true;
		}
	}

	SLanguage *pLanguage = new SLanguage;
	m_languages.push_back( pLanguage );

	if (m_cvarLocalizationDebug >= 2)
		CryLog("<Localization> Insert new language to %s", sLanguage);

	pLanguage->sLanguage = sLanguage;
	InternalSetCurrentLanguage(pLanguage);

	//------------------------------------------------------------------------------------------------- 
	// input localization
	//------------------------------------------------------------------------------------------------- 
	// keyboard
	for (int i = 0; i <= 0x80; i++)
	{
		AddControl(i);
	}

	// mouse
	for (int i = 1; i <= 0x0f; i++)
	{
		AddControl(i*0x10000);
	}

	return (true);
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::AddControl(int nKey)
{
	//marcok: currently not implemented ... will get fixed soon
	/*IInput *pInput = m_pSystem->GetIInput();

	if (!pInput)
	{
		return;
	}

	wchar_t szwKeyName[256] = {0};
	char		szKey[256] = {0};

	if (!IS_MOUSE_KEY(nKey))
	{
		if (pInput->GetOSKeyName(nKey, szwKeyName, 255))
		{
			sprintf(szKey, "control%d", nKey);

			SLocalizedStringEntry loc;
			loc.sEnglish.Format( "%S",szwKeyName );
			loc.sLocalized = szwKeyName;
			loc.sKey = szKey;
			AddLocalizedString( m_pLanguage,loc );
		}
	}*/
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::ParseFirstLine( IXmlTableReader *pXmlTableReader, char *nCellIndexToType, std::map<int, string> &SoundMoodIndex, std::map<int, string> &EventParameterIndex )
{
	string sCellContent;

	for (;;)
	{
		int nCellIndex = 0;
		const char* pContent = 0;
		size_t contentSize = 0;
		if (!pXmlTableReader->ReadCell(nCellIndex, pContent, contentSize))
			break;

		if (nCellIndex >= MAX_CELL_COUNT)
			break;

		if (contentSize <= 0)
			continue;

		sCellContent.assign(pContent, contentSize);
		sCellContent.MakeLower();

		for (int i = 0; i < sizeof(sLocalizedColumnNames)/sizeof(sLocalizedColumnNames[0]); ++i)
		{
			const char *pFind = strstr(sCellContent.c_str(), sLocalizedColumnNames[i]);
			if ( pFind != 0)
			{
				nCellIndexToType[nCellIndex] = i;

				// find SoundMood
				if (i == ELOCALIZED_COLUMN_SOUNDMOOD)
				{
					const char *pSoundMoodName = pFind + strlen(sLocalizedColumnNames[i]) + 1;
					int nSoundMoodNameLength = sCellContent.length() - strlen(sLocalizedColumnNames[i]) - 1;
					if (nSoundMoodNameLength > 0)
					{
						SoundMoodIndex[nCellIndex] = pSoundMoodName;
					}
				}

				// find EventParameter
				if (i == ELOCALIZED_COLUMN_EVENTPARAMETER)
				{
					const char *pParameterName = pFind + strlen(sLocalizedColumnNames[i]) + 1;
					int nParameterNameLength = sCellContent.length() - strlen(sLocalizedColumnNames[i]) - 1;
					if (nParameterNameLength > 0)
					{
						EventParameterIndex[nCellIndex] = pParameterName;
					}
				}

				break;
			}
			// HACK until all columns are renamed to "Translation"
			//if (stricmp(sCellContent, "Your Translation") == 0)
			//{
			//	nCellIndexToType[nCellIndex] = ELOCALIZED_COLUMN_TRANSLATED_ACTOR_LINE;
			//	break;
			//}
		}
	}
}

// copy characters to lower-case 0-terminated buffer
static void CopyLowercase(char* dst, size_t dstSize, const char* src, size_t srcCount)
{
	if (dstSize > 0)
	{
		if (srcCount > dstSize - 1)
		{
			srcCount = dstSize - 1;
		}
		while (srcCount--)
		{
			const char c = *src++;
			*dst++ = (c <= 'Z' && c >= 'A') ? c + ('a' - 'A') : c;
		}
		*dst = '\0';
	}
}

static void ReplaceEndOfLine(string& s)
{
	const string oldSubstr("\\n");
	const string newSubstr(" \n");
	size_t pos = 0;
	for (;;)
	{
		pos = s.find(oldSubstr, pos);
		if (pos == string::npos)
		{
			return;
		}
		s.replace(pos, oldSubstr.length(), newSubstr);
	}
}

//////////////////////////////////////////////////////////////////////////

void CLocalizedStringsManager::OnSystemEvent( 
	ESystemEvent eEvent,UINT_PTR wparam,UINT_PTR lparam )
{	
	// might want to add an event which tells us that we are loading the main menu
	// so everything can be unloaded and init files reloaded so safe some memory
	switch (eEvent)
	{	
	case ESYSTEM_EVENT_LEVEL_LOAD_START:
		{
			// This event is here not of interest while we're in the Editor.
			if (!gEnv->IsEditor())
			{
				if (m_cvarLocalizationDebug >= 2)
				{
					CryLog("<Localization> Loading Requested Tags");
				}

				for (TStringVec::iterator it = m_tagLoadRequests.begin(); it != m_tagLoadRequests.end(); ++it)
				{
					LoadLocalizationDataByTag(*it);
				}
			}

			m_tagLoadRequests.clear();
			break;
		}
	case ESYSTEM_EVENT_EDITOR_ON_INIT:
		{
			// Load all tags after the Editor has finished initialization.
			for (TTagFileNames::iterator it = m_tagFileNames.begin(); it != m_tagFileNames.end(); ++it)
			{
				LoadLocalizationDataByTag(it->first);
			}

			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////

bool CLocalizedStringsManager::InitLocalizationData( 
	const char* sFileName, bool bReload)
{
	XmlNodeRef root = m_pSystem->LoadXmlFromFile( sFileName );

	if (!root)
	{
		CryLog( "Loading Localization File %s failed!",sFileName );
		return false;
	}

	for (int i = 0; i < root->getChildCount(); i++)
	{
		XmlNodeRef typeNode = root->getChild(i);
		string sType = typeNode->getTag();
		
		// tags should be unique
		if (m_tagFileNames.find(sType) != m_tagFileNames.end())
			continue;

		TStringVec vEntries;
		for (int j = 0; j < typeNode->getChildCount(); j++)
		{
			XmlNodeRef entry = typeNode->getChild(j);
			if (!entry->isTag("entry"))
				continue;

			vEntries.push_back(entry->getContent());
		}

		CRY_ASSERT(m_tagFileNames.size() < 255);

		uint8 curNumTags = m_tagFileNames.size();

		m_tagFileNames[sType].filenames = vEntries;
		m_tagFileNames[sType].id				= curNumTags+1;

	}

	return true;
}

bool CLocalizedStringsManager::RequestLoadLocalizationDataByTag(const char* sTag)
{
	TTagFileNames::iterator it = m_tagFileNames.find(sTag);
	if (it == m_tagFileNames.end())
		return false;

	if (!gEnv->IsEditor())
	{
		LoadLocalizationDataByTag(sTag);
	}
	else
	{
		m_tagLoadRequests.push_back(sTag);
	}

	return true;
}

bool CLocalizedStringsManager::LoadLocalizationDataByTag(
	const char* sTag, bool bReload)
{
	TTagFileNames::iterator it = m_tagFileNames.find(sTag);
	if (it == m_tagFileNames.end())
		return false;

	bool bResult = true;

	stack_string const sLocalizationFolder(PathUtil::GetLocalizationFolder());

	TStringVec& vEntries = it->second.filenames;
	if (m_pLanguage)
	{
		uint32 nEntries = 0;
		for (TStringVec::iterator it2 = vEntries.begin(); it2 != vEntries.end(); ++it2)
		{
			nEntries += CountEntriesFromXmlSpreadsheet(sLocalizationFolder.c_str() + *it2);
		}
		m_pLanguage->m_vLocalizedStrings.reserve(m_pLanguage->m_vLocalizedStrings.size() + nEntries);
	}

	for (TStringVec::iterator it2 = vEntries.begin(); it2 != vEntries.end(); ++it2)
	{
		bResult &= DoLoadExcelXmlSpreadsheet(sLocalizationFolder.c_str() + *it2, it->second.id, bReload);
	}

	return bResult;
}

bool CLocalizedStringsManager::ReleaseLocalizationDataByTag(
	const char* sTag)
{
	TTagFileNames::iterator it = m_tagFileNames.find(sTag);
	if (it == m_tagFileNames.end())
		return false;

	const uint8 nTagID = it->second.id;

	tmapFilenames newLoadedTables;
	for (tmapFilenames::iterator iter = m_loadedTables.begin(); iter != m_loadedTables.end(); iter++)
	{
		if (iter->second.nTagID != nTagID)
		{
			newLoadedTables[iter->first] = iter->second;
		}
	}
	m_loadedTables = newLoadedTables;

	if (m_pLanguage)
	{
		const uint32 numEntries = m_pLanguage->m_vLocalizedStrings.size();
		for(int32 i=numEntries-1; i>=0; i--)
		{
			SLocalizedStringEntry *entry = m_pLanguage->m_vLocalizedStrings[i];
			if (entry->nTagID == nTagID)
			{
				m_pLanguage->m_keysMap.erase(entry->sKey);
				delete(entry);
				m_pLanguage->m_vLocalizedStrings.erase(m_pLanguage->m_vLocalizedStrings.begin()+i);
			}
		}
	}

	return true; 
}

uint32 CLocalizedStringsManager::CountEntriesFromXmlSpreadsheet( const char* sFileName )
{
	if (!m_pLanguage)
		return 0;

	IXmlTableReader* const pXmlTableReader = m_pSystem->GetXmlUtils()->CreateXmlTableReader();
	if (!pXmlTableReader)
	{
		CryLog( "Loading Localization File %s failed (XML system failure)!",sFileName );
		return 0;
	}

	XmlNodeRef root = m_pSystem->LoadXmlFromFile( sFileName );
	if (!root)
	{
		CryLog( "Loading Localization File %s failed!",sFileName );
		pXmlTableReader->Release();
		return 0;
	}

	int rowCount = 0;
	if (pXmlTableReader->Begin(root))
	{
		rowCount = pXmlTableReader->GetEstimatedRowCount();
		if (rowCount < 0)
		{
			rowCount = 0;
		}
	}

	pXmlTableReader->Release();

	return (uint32)rowCount;
}

bool CLocalizedStringsManager::LoadExcelXmlSpreadsheet( const char *sFileName, bool bReload )
{
	return DoLoadExcelXmlSpreadsheet( sFileName, 0, bReload );
}

//////////////////////////////////////////////////////////////////////
// Loads a string-table from a Excel XML Spreadsheet file.
bool CLocalizedStringsManager::DoLoadExcelXmlSpreadsheet( const char *sFileName, uint8 nTagID, bool bReload )
{
	LOADING_TIME_PROFILE_SECTION
	MEMSTAT_CONTEXT(EMemStatContextTypes::MSC_Other, 0, "Localization XMLs");
	MEMSTAT_CONTEXT_FMT(EMemStatContextTypes::MSC_Other, 0, "Localization XML (%s)", sFileName);

	if (!m_pLanguage)
		return false;

	//check if this table has already been loaded
	if (!bReload)
	{
		if (m_loadedTables.find(CONST_TEMP_STRING(sFileName)) != m_loadedTables.end())
			return (true);
	}

	IXmlTableReader* const pXmlTableReader = m_pSystem->GetXmlUtils()->CreateXmlTableReader();
	if (!pXmlTableReader)
	{
		CryLog( "Loading Localization File %s failed (XML system failure)!",sFileName );
		return false;
	}

	XmlNodeRef root = m_pSystem->LoadXmlFromFile( sFileName );
	if (!root)
	{
		CryLog( "Loading Localization File %s failed!",sFileName );
		pXmlTableReader->Release();
		return false;
	}

	// bug search, re-export to a file to compare it
	//string sReExport = sFileName;
	//sReExport += ".re";
	//root->saveToFile(sReExport.c_str());

	CryLog( "Loading Localization File %s",sFileName );
	INDENT_LOG_DURING_SCOPE();

	if (!pXmlTableReader->Begin(root))
	{
		CryLog( "Loading Localization File %s failed! The file is in an unsupported format.",sFileName );
		pXmlTableReader->Release();
		return false;
	}

	pairFileName sNewFile;
	sNewFile.first = sFileName;
	sNewFile.second.bDataStripping = false; // this is off for now
	sNewFile.second.nTagID = nTagID; 
	m_loadedTables.insert(sNewFile);

	// Cell Index
	char nCellIndexToType[MAX_CELL_COUNT];
	memset( nCellIndexToType,0,sizeof(nCellIndexToType) );

	// SoundMood Index
	std::map<int, string> SoundMoodIndex;

	// EventParameter Index
	std::map<int, string> EventParameterIndex;

	bool bFirstRow = true;

	string sTmp;

	// lower case event name
	char szLowerCaseEvent[128];
	// lower case key
	char szLowerCaseKey[1024];

	size_t nMemSize = 0;

	for (;;)
	{
		int nRowIndex = -1;
		if (!pXmlTableReader->ReadRow(nRowIndex))
			break;

		if (bFirstRow)
		{
			bFirstRow = false;
			ParseFirstLine(pXmlTableReader, nCellIndexToType, SoundMoodIndex, EventParameterIndex);
			// Skip first row, it contains description only.
			continue;
		}

		bool bValidKey = false;
		bool bValidTranslatedText = false;
		bool bValidTranslatedCharacterName = false;
		bool bValidTranslatedActorLine = false;
		bool bUseSubtitle = true;
		bool bIsDirectRadio = false;
		bool bIsIntercepted = false;

		struct CConstCharArray
		{
			const char* ptr;
			size_t count;

			CConstCharArray()
			{
				clear();
			}
			void clear()
			{
				ptr = "";
				count = 0;
			}
			bool empty() const
			{
				return count == 0;
			}
		};

		CConstCharArray sKeyString;
		CConstCharArray sOriginalCharacterName;
		CConstCharArray sTranslatedCharacterName;
		CConstCharArray sOriginalText;
		CConstCharArray sTranslatedText;
		CConstCharArray sOriginalActorLine;
		CConstCharArray sTranslatedActorLine;
		CConstCharArray sSoundEvent;

		float fVolume = 1.0f;
		float fRadioRatio = 1.0f;
		float fEventParameterValue = 0.0f;
		float fSoundMoodValue = 0.0f;

		int nItems = 0;
		std::map<int, float> SoundMoodValues;
		std::map<int, float> EventParameterValues;

		for (;;)
		{
			int nCellIndex = -1;
			CConstCharArray cell;

			if (!pXmlTableReader->ReadCell(nCellIndex, cell.ptr, cell.count))
				break;

			if (nCellIndex >= MAX_CELL_COUNT)
				break;

			// skip empty cells
			if (cell.count <= 0)
				continue;

			const char nCellType = nCellIndexToType[nCellIndex];

			switch (nCellType)
			{
			case ELOCALIZED_COLUMN_SKIP:
				break;
			case ELOCALIZED_COLUMN_KEY:
				sKeyString = cell;
				bValidKey = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_AUDIOFILE:
				sKeyString = cell;
				bValidKey = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_ORIGINAL_CHARACTER_NAME:
				sOriginalCharacterName = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_TRANSLATED_CHARACTER_NAME:
				sTranslatedCharacterName = cell;
				bValidTranslatedCharacterName = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_ORIGINAL_TEXT:
				sOriginalText = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_TRANSLATED_TEXT:
				sTranslatedText = cell;
				bValidTranslatedText = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_ORIGINAL_ACTOR_LINE:
				sOriginalActorLine = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_TRANSLATED_ACTOR_LINE:
				sTranslatedActorLine = cell;
				bValidTranslatedActorLine = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_USE_SUBTITLE:
				sTmp.assign(cell.ptr, cell.count);
				bUseSubtitle = CryStringUtils::toYesNoType(sTmp.c_str()) == CryStringUtils::nYNT_No ? false : true; // favor yes (yes and invalid -> yes)
				break;
			case ELOCALIZED_COLUMN_VOLUME:
				sTmp.assign(cell.ptr, cell.count);
				fVolume = (float)atof(sTmp);
				++nItems;
				break;
			case ELOCALIZED_COLUMN_SOUNDEVENT:
				sSoundEvent = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_RADIO_RATIO:
				sTmp.assign(cell.ptr, cell.count);
				fRadioRatio = (float)atof(sTmp);
				++nItems;
				break;
			case ELOCALIZED_COLUMN_EVENTPARAMETER:
				sTmp.assign(cell.ptr, cell.count);
				fEventParameterValue = (float)atof(sTmp);
				EventParameterValues[nCellIndex] = fEventParameterValue;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_SOUNDMOOD:
				sTmp.assign(cell.ptr, cell.count);
				fSoundMoodValue = (float)atof(sTmp);
				SoundMoodValues[nCellIndex] = fSoundMoodValue;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_IS_DIRECT_RADIO:
				sTmp.assign(cell.ptr, cell.count);
				if(!stricmp(sTmp.c_str(), "intercept"))
				{
					bIsIntercepted = true;
				}
				bIsDirectRadio = bIsIntercepted || (CryStringUtils::toYesNoType(sTmp.c_str()) == CryStringUtils::nYNT_Yes ? true : false); // favor no (no and invalid -> no)
				++nItems;
				break;
			// legacy names
			case ELOCALIZED_COLUMN_LEGACY_PERSON:
				// old file often only have content in this column
				if (!cell.empty())
				{
					sOriginalCharacterName = cell;
					sTranslatedCharacterName = cell;
					bValidTranslatedCharacterName = true;
				}
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_CHARACTERNAME:
				sOriginalCharacterName = cell;
				sTranslatedCharacterName = cell;
				bValidTranslatedCharacterName = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_CHARACTERNAME:
				sTranslatedCharacterName = cell;
				bValidTranslatedCharacterName = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_ENGLISH_DIALOGUE:
				// old file often only have content in this column
				sOriginalActorLine = cell;
				sOriginalText = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_TRANSLATION:
				sTranslatedActorLine = cell;
				sTranslatedText = cell;
				bValidTranslatedText = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_YOUR_TRANSLATION:
				sTranslatedActorLine = cell;
				sTranslatedText = cell;
				bValidTranslatedText = true;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_ENGLISH_SUBTITLE:
				sOriginalText = cell;
				++nItems;
				break;
			case ELOCALIZED_COLUMN_LEGACY_TRANSLATED_SUBTITLE:
				sTranslatedText = cell;
				sTranslatedActorLine = cell;
				bValidTranslatedText = true;
				++nItems;
				break;
			}
		}

		if (!bValidKey)
			continue;

		if (!bValidTranslatedText)
		{
			// if this is a dialog entry with a soundevent and with subtitles then a warning should be issued
			if (m_cvarLocalizationDebug && !sSoundEvent.empty() && bUseSubtitle)
			{
				sTmp.assign(sKeyString.ptr, sKeyString.count);
				CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[LocError] Key '%s' in file <%s> has no translated text", sTmp.c_str(), sFileName);
			}

			// use translated actor line entry if available before falling back to original entry
			if (!sTranslatedActorLine.empty())
				sTranslatedText = sTranslatedActorLine;
			else
				sTranslatedText = sOriginalText;
		}

		if (!bValidTranslatedActorLine)
		{
			// if this is a dialog entry with a soundevent then a warning should be issued
			if (m_cvarLocalizationDebug && !sSoundEvent.empty())
			{
				sTmp.assign(sKeyString.ptr, sKeyString.count);
				CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[LocError] Key '%s' in file <%s> has no translated actor line", sTmp.c_str(), sFileName);
			}

			// use translated text entry if available before falling back to original entry
			if (!sTranslatedText.empty())
				sTranslatedActorLine = sTranslatedText;
			else
				sTranslatedActorLine = sOriginalText;
		}

		if (!sSoundEvent.empty() && !bValidTranslatedCharacterName)
		{
			if (m_cvarLocalizationDebug)
			{
				sTmp.assign(sKeyString.ptr, sKeyString.count);
				CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[LocError] Key '%s' in file <%s> has no translated character name", sTmp.c_str(), sFileName);
			}

			sTranslatedCharacterName = sOriginalCharacterName;
		}

		if (nItems==1) // skip lines which contain just one item in the key
			continue;

		// reject to store text if line was marked with no subtitles in game mode
		if (!gEnv->IsEditor())
		{
			if (!bUseSubtitle)
			{
				sOriginalText.clear();
				sTranslatedText.clear();
			}
		}

		// Skip @ character in the key string.
		if (!sKeyString.empty() && sKeyString.ptr[0] == '@')
		{
			sKeyString.ptr++;
			sKeyString.count--;
		}

		CopyLowercase(szLowerCaseEvent, sizeof(szLowerCaseEvent), sSoundEvent.ptr, sSoundEvent.count);
		CopyLowercase(szLowerCaseKey, sizeof(szLowerCaseKey), sKeyString.ptr, sKeyString.count);

		if (m_pLanguage->m_keysMap.find(CONST_TEMP_STRING(szLowerCaseKey)) != m_pLanguage->m_keysMap.end())
		{
			sTmp.assign(sKeyString.ptr, sKeyString.count);
			CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[LocError] Localized String '%s' Already Loaded for Language %s", sTmp.c_str(),m_pLanguage->sLanguage.c_str());
			continue;
		}

		SLocalizedStringEntry *pEntry = new SLocalizedStringEntry;
		pEntry->sKey = szLowerCaseKey;
		pEntry->bUseSubtitle = bUseSubtitle;
		pEntry->nRow = nRowIndex;
		pEntry->nTagID = nTagID;

		if (!sOriginalActorLine.empty() && gEnv->IsEditor())
		{
			sTmp.assign(sOriginalActorLine.ptr, sOriginalActorLine.count);
			ReplaceEndOfLine(sTmp);
			pEntry->sOriginalActorLine = sTmp;
		}
		if (!sTranslatedActorLine.empty() && gEnv->IsEditor())
		{
			sTmp.assign(sTranslatedActorLine.ptr, sTranslatedActorLine.count);
			ReplaceEndOfLine(sTmp);
			AppendToUnicodeString( pEntry->swTranslatedActorLine,sTmp );
		}
		if (bUseSubtitle && !sOriginalText.empty() && gEnv->IsEditor())
		{
			sTmp.assign(sOriginalText.ptr, sOriginalText.count);
			ReplaceEndOfLine(sTmp);
			pEntry->sOriginalText = sTmp;
		}

		if (bUseSubtitle && !sTranslatedText.empty())
		{
			sTmp.assign(sTranslatedText.ptr, sTranslatedText.count);
			ReplaceEndOfLine(sTmp);
			AppendToUnicodeString( pEntry->swTranslatedText, sTmp );
		}

		// the following is used to cleverly assign strings
		// we store all known string into the m_prototypeEvents set and assign known entries from there
		// the CryString makes sure, that only the ref-count is increment on assignment
		if (*szLowerCaseEvent)
		{
			PrototypeSoundEvents::iterator it = m_prototypeEvents.find( CONST_TEMP_STRING(szLowerCaseEvent) );
			if (it != m_prototypeEvents.end())
				pEntry->sPrototypeSoundEvent = *it;
			else
			{
				pEntry->sPrototypeSoundEvent = szLowerCaseEvent;
				m_prototypeEvents.insert(pEntry->sPrototypeSoundEvent);
			}
		}

		// only use the translated character name
		if (gEnv->IsEditor())
			pEntry->sOriginalCharacterName.assign(sOriginalCharacterName.ptr, sOriginalCharacterName.count);

		const CConstCharArray sWho = sTranslatedCharacterName.empty() ? sOriginalCharacterName : sTranslatedCharacterName;
		if (!sWho.empty())
		{
			sTmp.assign(sWho.ptr, sWho.count);
			ReplaceEndOfLine(sTmp);
			wstring tmp;
			AppendToUnicodeString(tmp, sTmp);
			CharacterNameSet::iterator it = m_characterNameSet.find(tmp);
			if (it != m_characterNameSet.end())
				pEntry->swTranslatedCharacterName = *it;
			else
			{
				pEntry->swTranslatedCharacterName = tmp;
				m_characterNameSet.insert(pEntry->swTranslatedCharacterName);
			}
		}

		pEntry->fVolume = fVolume;

		// SoundMood Entries
		pEntry->SoundMoods.resize(SoundMoodValues.size());
		if (SoundMoodValues.size() > 0)
		{
			std::map<int, float>::const_iterator itEnd = SoundMoodValues.end();
			int nSoundMoodCount = 0;
			for(std::map<int, float>::const_iterator it = SoundMoodValues.begin(); it != itEnd; ++it)
			{
				pEntry->SoundMoods[nSoundMoodCount].fValue = (*it).second;
				pEntry->SoundMoods[nSoundMoodCount].sName = SoundMoodIndex[(*it).first];
				++nSoundMoodCount;
			}
		}

		// EventParameter Entries
		pEntry->EventParameters.resize(EventParameterValues.size());
		if (EventParameterValues.size() > 0)
		{
			std::map<int, float>::const_iterator itEnd = EventParameterValues.end();
			int nEventParameterCount = 0;
			for(std::map<int, float>::const_iterator it = EventParameterValues.begin(); it != itEnd; ++it)
			{
				pEntry->EventParameters[nEventParameterCount].fValue = (*it).second;
				pEntry->EventParameters[nEventParameterCount].sName = EventParameterIndex[(*it).first];
				++nEventParameterCount;
			}
		}

		pEntry->fRadioRatio = fRadioRatio;

		pEntry->bIsDirectRadio = bIsDirectRadio;
		pEntry->bIsIntercepted = bIsIntercepted;

		nMemSize += sizeof(*pEntry) + pEntry->sKey.length() 
			+ pEntry->sOriginalActorLine.length() + pEntry->swTranslatedActorLine.length()*sizeof(wchar_t) 
			+ pEntry->sOriginalText.length() + pEntry->swTranslatedText.length()*sizeof(wchar_t) 
			+ pEntry->sOriginalCharacterName.length() + pEntry->swTranslatedCharacterName.length()*sizeof(wchar_t);
		

		// Compression Preparation
		//unsigned int nSourceSize = pEntry->swTranslatedText.length()*sizeof(wchar_t);
		//if (nSourceSize)
		//	int zResult = Compress(pDest, nDestLen, pEntry->swTranslatedText.c_str(), nSourceSize);

		AddLocalizedString( m_pLanguage,pEntry );
	}

	pXmlTableReader->Release();

	return true;
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::ReloadData()
{
	tmapFilenames temp = m_loadedTables;

	FreeLocalizationData();
	for (tmapFilenames::iterator it = temp.begin(); it != temp.end(); it++)
	{
		DoLoadExcelXmlSpreadsheet( (*it).first, (*it).second.nTagID, true );
	}
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::AddLocalizedString( SLanguage *pLanguage,SLocalizedStringEntry *pEntry )
{
	pLanguage->m_vLocalizedStrings.push_back(pEntry);
	int nId = (int)pLanguage->m_vLocalizedStrings.size()-1;
	pLanguage->m_keysMap[pEntry->sKey] = pEntry;

	if (m_cvarLocalizationDebug >= 2)
		CryLog("<Localization> Add new string <%s> with ID %d to <%s>", pEntry->sKey.c_str(), nId, pLanguage->sLanguage.c_str());
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::LocalizeString(const char* sString, wstring& outLocalizedString, bool bEnglish)
{ 	
	string tmpString(sString);
	return LocalizeString(tmpString, outLocalizedString, bEnglish);
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::LocalizeString(const string& sString, wstring& outLocalizedString, bool bEnglish)
{ 	
	assert (m_pLanguage);
	if (m_pLanguage == 0)
	{
		CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"LocalizeString: No language set.");
		AppendToUnicodeString(outLocalizedString, sString);
		return false;
	}

	outLocalizedString.assign (L"");

	// scan the string
	bool done = false;

	int len = sString.length();
	int curpos = 0;
	while (!done)
	{
		int pos = sString.find_first_of("@", curpos);
		if (pos == string::npos)
		{
			// did not find anymore, so we are done
			done = true;
			pos = len;
		}
		// found an occurrence

		// we have skipped a few characters, so copy them over
		if (pos > curpos)
		{
			// skip
			AppendToUnicodeString( outLocalizedString,sString.substr(curpos, pos - curpos) );
			curpos = pos;
		}

		if (curpos == pos)
		{
			// find the end of the label
			int endpos = sString.find_first_of(" ", curpos);
			if (endpos == string::npos)
			{
				// have reached the end
				done = true;
				endpos = len;
			}

			if (endpos > curpos)
			{
				// localize token		
				if (bEnglish)
				{
					string sLocalizedToken;
					GetEnglishString(sString.substr(curpos, endpos - curpos).c_str(), sLocalizedToken);
					AppendToUnicodeString( outLocalizedString,sLocalizedToken );
				}
				else
				{
					wstring sLocalizedToken;
					LocalizeLabel(sString.substr(curpos, endpos - curpos), sLocalizedToken);
					// append
					outLocalizedString+=sLocalizedToken;
				}			

				curpos = endpos;
			}
		}
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
class CTempLocStringStorage : public ITempLocStringStorage
{
public:
	VIRTUAL void Release() { delete this; }
	VIRTUAL const wchar_t* GetCStr() const { return m_str.c_str(); }

public:
	CTempLocStringStorage() : m_str() {}
	wstring& GetString() { return m_str; }

private:
	virtual ~CTempLocStringStorage() {}

private:
	wstring m_str;
};

void CLocalizedStringsManager::LocalizeLabelAssignOutput(const char* pStr, wstring& regularOut, ITempLocStringStorage** ppTmpOut)
{
	assert(pStr);

	const size_t strLen = pStr ? strlen(pStr) : 0;
	const size_t capAvail = regularOut.capacity(); // doesn't count null termination

	CTempLocStringStorage* pTmpStorage = 0;
	if (ppTmpOut && capAvail < strLen)
		pTmpStorage = new CTempLocStringStorage;

	m_tempWString.resize(strLen+1);
#if defined(LINUX) || defined(PS3) || defined(CAFE)
	mbstowcs(&m_tempWString[0], pStr, strLen);
#else
	MultiByteToWideChar(CP_UTF8, 0, pStr, -1, &m_tempWString[0], strLen);
#endif
	m_tempWString[strLen] = L'\0';

	wstring& out = pTmpStorage ? pTmpStorage->GetString() : regularOut;
	out = &m_tempWString[0];

	if (ppTmpOut)
		*ppTmpOut = pTmpStorage;
}

void CLocalizedStringsManager::LocalizeLabelAssignOutput(const wchar_t* pStr, wstring& regularOut, ITempLocStringStorage** ppTmpOut)
{
	assert(pStr);

	const size_t strLen = pStr ? wcslen(pStr) : 0;
	const size_t capAvail = regularOut.capacity(); // doesn't count null termination

	CTempLocStringStorage* pTmpStorage = 0;
	if (ppTmpOut && capAvail < strLen)
		pTmpStorage = new CTempLocStringStorage;

	wstring& out = pTmpStorage ? pTmpStorage->GetString() : regularOut;
	out = pStr;

	if (ppTmpOut)
		*ppTmpOut = pTmpStorage;
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::LocalizeLabel(const char* sLabel, wstring& outLocalString, bool bEnglish, ITempLocStringStorage** ppTmpStorage)
{
	assert(sLabel);
	if (!m_pLanguage || !sLabel)
		return false;


	//In the editor, Localization gets initialized after the document is ready
	//scaleform might want to try and localize earlier (when flash movies are loaded for the UI emulator), this catch is just to suppress log spam in the editor
	if(gEnv->IsEditor() && m_pLanguage->m_vLocalizedStrings.empty())
		return false;
	
	// Label sign.
	if (sLabel[0] == '@')
	{
		SLocalizedStringEntry *entry = stl::find_in_map( m_pLanguage->m_keysMap,CONST_TEMP_STRING(sLabel+1),NULL ); // skip @ character.

		// if it continues with cc_ it's a control code
		if (sLabel[1] && (sLabel[1] == 'c' || sLabel[1] == 'C') && 
			  sLabel[2] && (sLabel[2] == 'c' || sLabel[2] == 'C') && 
				sLabel[3] && sLabel[3] == '_')
		{
			if (entry == NULL)
			{
				// controlcode
				// lookup KeyName
				IInput* pInput = gEnv->pInput;
				SInputEvent ev;
				ev.deviceId = eDI_Keyboard;
				ev.keyName = sLabel+4; // skip @cc_
				char inputCharAscii = pInput->GetInputCharAscii(ev);

				if (inputCharAscii == 0)
				{
					// try OS key
					const wchar_t* wKeyName = pInput->GetOSKeyName(ev);
					if (wKeyName && *wKeyName)
					{
						LocalizeLabelAssignOutput(wKeyName, outLocalString, ppTmpStorage);
						return true;
					}
					// if we got some empty, try non-keyboard as well
					ev.deviceId = eDI_Unknown;
					inputCharAscii = pInput->GetInputCharAscii(ev);
				}
				
				if (inputCharAscii)
				{
					wchar_t tmpString[2];
					tmpString[0] = 0;
#if defined(LINUX) || defined(PS3) || defined(CAFE)
					mbstowcs( tmpString, &inputCharAscii, 1 );
#else
					MultiByteToWideChar( CP_ACP, 0, &inputCharAscii, -1, tmpString, 1 );
#endif
					tmpString[0] = ToUpperCase(tmpString[0]); // use localized version!
					tmpString[1] = 0;
					LocalizeLabelAssignOutput(tmpString, outLocalString, ppTmpStorage);
					return true;
				}
			}
			// we found an localized entry (e.g. @cc_space), use normal translation -> Space/Leertaste
		}

		if (entry != NULL)
		{
			if (bEnglish || entry->swTranslatedText.empty())
			{
				//assert(!"No Localization Text availible!");
				LocalizeLabelAssignOutput(entry->sOriginalText.c_str(), outLocalString, ppTmpStorage);
				return true;
			}
			else
			{
				LocalizeLabelAssignOutput(entry->swTranslatedText.c_str(), outLocalString, ppTmpStorage);
			}
			return true;
		}
		else 
		{
			CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Localized string for Label <%s> not found", sLabel );
		}
	}
	else
	{
		CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Not a valid localized string Label <%s>, must start with @ symbol", sLabel );
	}

	LocalizeLabelAssignOutput(sLabel, outLocalString, ppTmpStorage);

	return false;
}


//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetEnglishString( const char *sKey, string &sLocalizedString )
{
	assert(sKey);
	if (!m_pLanguage || !sKey)
		return false;

	// Label sign.
	if (sKey[0] == '@')
	{
		SLocalizedStringEntry *entry = stl::find_in_map( m_pLanguage->m_keysMap,CONST_TEMP_STRING(sKey+1), NULL ); // skip @ character.
		if (entry != NULL)
		{
			sLocalizedString = entry->sOriginalText;
			return true;
		}
		else
		{
			entry = stl::find_in_map( m_pLanguage->m_keysMap,CONST_TEMP_STRING(sKey), NULL ); 
			if (entry != NULL)
			{
				sLocalizedString = entry->sOriginalText;
				return true;
			}
			else
			{
				// CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Localized string for Label <%s> not found", sKey );
				sLocalizedString = sKey;
				return false;
			}
		}
	}
	else
	{
		// CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Not a valid localized string Label <%s>, must start with @ symbol", sKey );
	}

	sLocalizedString = sKey;
	return false;
}


//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::AppendToUnicodeString( wstring &sDest,const string& sSource )
{
	size_t len = sSource.length();
	m_tempWString.resize(len+1);   // post-cond: m_tempWString.capacity() >= maxSize
	wchar_t* tmpString = m_tempWString.begin();
	tmpString[0] = L'\0';

#if defined(LINUX) || defined(PS3) || defined(CAFE)
	mbstowcs( tmpString, sSource.c_str(), len );
	tmpString[len] = L'\0';
#else
	MultiByteToWideChar( CP_UTF8,0,sSource.c_str(),-1,tmpString,len );
	tmpString[len] = L'\0';
#endif

	sDest += tmpString;

/*
#if 1
	size_t maxSize = sSource.length() + 1;
	m_tempWString.reserve(maxSize);   // post-cond: m_tempWString.capacity() >= maxSize
	wchar_t* tmpString = m_tempWString.begin();
	wchar_t* d = tmpString;
	const char* s = sSource.c_str();
	while (--maxSize > 0) // NOTE: preincrement, because maxSize = length+1
		mbtowc(d++, s++, 1);
	*d = L'\0';
#else
	size_t len = sSource.length();
	m_tempWString.reserve(len+1);   // post-cond: m_tempWString.capacity() >= maxSize
	wchar_t* tmpString = m_tempWString.begin();
	tmpString[0] = L'\0';

#if defined(LINUX) || defined(PS3)
	mbstowcs( tmpString, sSource.c_str(), len );
	tmpString[len] = L'\0';
#else
	MultiByteToWideChar( CP_UTF8,0,sSource.c_str(),-1,tmpString,len );
#endif

#endif
	// append to dest string
	sDest += tmpString;
	*/
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetLocalizedInfoByKey(const char* sKey, SLocalizedInfoGame& outGameInfo)
{
	if (!m_pLanguage)
		return false;
	const SLocalizedStringEntry *entry = stl::find_in_map(m_pLanguage->m_keysMap, CONST_TEMP_STRING(sKey), NULL);
	if (entry != NULL)
	{
		outGameInfo.sKey = entry->sKey;
		outGameInfo.swTranslatedCharacterName = entry->swTranslatedCharacterName;
		outGameInfo.swTranslatedText = entry->swTranslatedText;

		outGameInfo.nRow = entry->nRow;
		outGameInfo.bUseSubtitle = entry->bUseSubtitle;

		return true;
	}
	else
		return false;
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetLocalizedInfoByKey(const char *sKey, SLocalizedSoundInfoGame* pOutSoundInfo)
{
	assert(sKey);
	if (!m_pLanguage || !sKey || !pOutSoundInfo)
		return false;

	bool bResult = false;

	const SLocalizedStringEntry *pEntry = stl::find_in_map(m_pLanguage->m_keysMap, CONST_TEMP_STRING(sKey), NULL);
	if (pEntry != NULL)
	{
		bResult = true;

		pOutSoundInfo->swTranslatedCharacterName = pEntry->swTranslatedCharacterName;
		pOutSoundInfo->swTranslatedText = pEntry->swTranslatedText;

		//pOutSoundInfo->sOriginalActorLine = pEntry->sOriginalActorLine.c_str();
		//pOutSoundInfo->sTranslatedActorLine = pEntry->swTranslatedActorLine.c_str();
		//pOutSoundInfo->sOriginalText = pEntry->sOriginalText;
		// original Character

		pOutSoundInfo->sSoundEvent = pEntry->sPrototypeSoundEvent.c_str();
		pOutSoundInfo->fVolume = pEntry->fVolume;
		pOutSoundInfo->fRadioRatio = pEntry->fRadioRatio;
		pOutSoundInfo->bUseSubtitle = pEntry->bUseSubtitle;
		pOutSoundInfo->bIsDirectRadio = pEntry->bIsDirectRadio;
		pOutSoundInfo->bIsIntercepted = pEntry->bIsIntercepted;

		//SoundMoods
		if (pOutSoundInfo->nNumSoundMoods >= pEntry->SoundMoods.size())
		{
			// enough space to copy data
			int i = 0;
			for (; i<pEntry->SoundMoods.size(); ++i)
			{
				pOutSoundInfo->pSoundMoods[i].sName = pEntry->SoundMoods[i].sName;
				pOutSoundInfo->pSoundMoods[i].fValue = pEntry->SoundMoods[i].fValue;
			}
			// if mode is available fill it with default
			for (; i<pOutSoundInfo->nNumSoundMoods; ++i)
			{
				pOutSoundInfo->pSoundMoods[i].sName = "";
				pOutSoundInfo->pSoundMoods[i].fValue = 0.0f;
			}
			pOutSoundInfo->nNumSoundMoods = pEntry->SoundMoods.size();
		}
		else
		{
			// not enough memory, say what is needed
			pOutSoundInfo->nNumSoundMoods = pEntry->SoundMoods.size();
			bResult = (pOutSoundInfo->pSoundMoods == NULL); // only report error if memory was provided but is too small
		}

		//EventParameters
		if (pOutSoundInfo->nNumEventParameters >= pEntry->EventParameters.size())
		{
			// enough space to copy data
			int i = 0;
			for (; i<pEntry->EventParameters.size(); ++i)
			{
				pOutSoundInfo->pEventParameters[i].sName = pEntry->EventParameters[i].sName;
				pOutSoundInfo->pEventParameters[i].fValue = pEntry->EventParameters[i].fValue;
			}
			// if mode is available fill it with default
			for (; i<pOutSoundInfo->nNumEventParameters; ++i)
			{
				pOutSoundInfo->pEventParameters[i].sName = "";
				pOutSoundInfo->pEventParameters[i].fValue = 0.0f;
			}
			pOutSoundInfo->nNumEventParameters = pEntry->EventParameters.size();
		}
		else
		{
			// not enough memory, say what is needed
			pOutSoundInfo->nNumEventParameters = pEntry->EventParameters.size();
			bResult = (pOutSoundInfo->pSoundMoods == NULL); // only report error if memory was provided but is too small
		}
	}

	return bResult;
}

//////////////////////////////////////////////////////////////////////////
int CLocalizedStringsManager::GetLocalizedStringCount()
{
	if (!m_pLanguage)
		return 0;
	return m_pLanguage->m_vLocalizedStrings.size();
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetLocalizedInfoByIndex(int nIndex, SLocalizedInfoGame& outGameInfo)
{
	if (!m_pLanguage)
		return false;
	const std::vector<SLocalizedStringEntry*>& entryVec = m_pLanguage->m_vLocalizedStrings;
	if (nIndex < 0 || nIndex >= (int)entryVec.size())
		return false;
	const SLocalizedStringEntry* pEntry = entryVec[nIndex];
	outGameInfo.sKey = pEntry->sKey;
	outGameInfo.swTranslatedCharacterName = pEntry->swTranslatedCharacterName;
	outGameInfo.swTranslatedText = pEntry->swTranslatedText;

	//outInfo.sOriginalActorLine = pEntry->sOriginalActorLine;
	//outInfo.swTranslatedActorLine = pEntry->swTranslatedActorLine;
	//outInfo.sOriginalText = pEntry->sOriginalText;
	//outInfo.sOriginalCharacterName = pEntry->sOriginalCharacterName;
	
	outGameInfo.nRow = pEntry->nRow;
	outGameInfo.bUseSubtitle = pEntry->bUseSubtitle;
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetLocalizedInfoByIndex(int nIndex, SLocalizedInfoEditor& outEditorInfo)
{
	if (!m_pLanguage)
		return false;
	const std::vector<SLocalizedStringEntry*>& entryVec = m_pLanguage->m_vLocalizedStrings;
	if (nIndex < 0 || nIndex >= (int)entryVec.size())
		return false;
	const SLocalizedStringEntry* pEntry = entryVec[nIndex];
	outEditorInfo.sKey = pEntry->sKey;
	outEditorInfo.swTranslatedCharacterName = pEntry->swTranslatedCharacterName;
	outEditorInfo.swTranslatedText = pEntry->swTranslatedText;

	outEditorInfo.sOriginalActorLine = pEntry->sOriginalActorLine;
	outEditorInfo.swTranslatedActorLine = pEntry->swTranslatedActorLine;

	//outEditorInfo.sOriginalText = pEntry->sOriginalText;
	outEditorInfo.sOriginalCharacterName = pEntry->sOriginalCharacterName;

	outEditorInfo.nRow = pEntry->nRow;
	outEditorInfo.bUseSubtitle = pEntry->bUseSubtitle;
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool CLocalizedStringsManager::GetSubtitle( const char* sKeyOrLabel, wstring& outSubtitle, bool bForceSubtitle)
{
	assert(sKeyOrLabel);
	if (!m_pLanguage || !sKeyOrLabel || !*sKeyOrLabel)
		return false;
	if (*sKeyOrLabel == '@')
		++sKeyOrLabel;
	const SLocalizedStringEntry *pEntry = stl::find_in_map(m_pLanguage->m_keysMap, CONST_TEMP_STRING(sKeyOrLabel), NULL);
	if (pEntry != NULL)
	{
		if (pEntry->bUseSubtitle == false && !bForceSubtitle)
			return false;

		// TODO verify that no fallback is needed
		
		outSubtitle = pEntry->swTranslatedText;

		if (pEntry->swTranslatedText.empty() == false)
			outSubtitle = pEntry->swTranslatedText;
		else if (pEntry->swTranslatedActorLine.empty() == false)
			outSubtitle = pEntry->swTranslatedActorLine;
		else if (pEntry->sOriginalText.empty() == false)
		{
			outSubtitle.resize(0);
			AppendToUnicodeString(outSubtitle, pEntry->sOriginalText);
		}
		else  if (pEntry->sOriginalActorLine.empty() == false)
		{
			outSubtitle.resize(0);
			AppendToUnicodeString(outSubtitle, pEntry->sOriginalActorLine);
		}
		else  if (pEntry->swTranslatedText.empty() == false)
		{
			outSubtitle.resize(0);
			outSubtitle = pEntry->swTranslatedText;
		}
		return true;
	}
	return false;
}

template<typename StringClass, typename CharType>
void _InternalFormatStringMessage(StringClass& outString, const StringClass& sString, const CharType** sParams, int nParams)
{
	static const CharType token = (CharType) '%';
	static const CharType tokens1[2] = { token, (CharType) '\0' };
	static const CharType tokens2[3] = { token, token, (CharType) '\0' };

	int maxArgUsed = 0;
	int lastPos = 0;
	int curPos = 0;
	const int sourceLen = sString.length();
	while (true)
	{
		int foundPos = sString.find(token, curPos);
		if (foundPos != string::npos)
		{
			if (foundPos+1 < sourceLen)
			{
				const int nArg = sString[foundPos+1] -'1';
				if (nArg >= 0 && nArg <= 9)
				{
					if (nArg < nParams)
					{
						outString.append(sString, lastPos, foundPos-lastPos);
						outString.append(sParams[nArg]);
						curPos = foundPos+2;
						lastPos = curPos;
						maxArgUsed = std::max(maxArgUsed, nArg);
					}
					else
					{
						StringClass tmp (sString);
						tmp.replace(tokens1, tokens2);
						if(sizeof(*tmp.c_str()) == sizeof(char))
							CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Parameter for argument %d is missing. [%s]", nArg+1, (const char*)tmp.c_str() );
						else
							CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Parameter for argument %d is missing. [%S]", nArg+1, (const wchar_t*)tmp.c_str() );
						curPos = foundPos+1;
					}
				}
				else
					curPos = foundPos+1;
			}
			else
				curPos = foundPos+1;
		}
		else
		{
			outString.append(sString, lastPos, sourceLen-lastPos );
			break;
		}
	}
	if (maxArgUsed < nParams-1)
	{
		StringClass tmp (sString);
		tmp.replace(tokens1, tokens2);
		if(sizeof(*tmp.c_str()) == sizeof(char))
			CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Not all parameters used for [%s] (Req:%d/Given:%d)", (const char*)tmp.c_str(), maxArgUsed+1, nParams );
		else
			CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"Not all parameters used for [%S] (Req:%d/Given:%d)", (const wchar_t*)tmp.c_str(), maxArgUsed+1, nParams );
	}
}

template<typename StringClass, typename CharType>
void _InternalFormatStringMessage(StringClass& outString, const StringClass& sString, const CharType* param1, const CharType* param2=0, const CharType* param3=0, const CharType* param4=0)
{
	static const int MAX_PARAMS = 4;
	const CharType* params[MAX_PARAMS] = { param1, param2, param3, param4 };
	int nParams = 0;
	while (nParams < MAX_PARAMS && params[nParams])
		++nParams;
	_InternalFormatStringMessage(outString, sString, params, nParams);
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FormatStringMessage(string& outString, const string& sString, const char** sParams, int nParams)
{
	_InternalFormatStringMessage(outString, sString, sParams, nParams);
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FormatStringMessage(string& outString, const string& sString, const char* param1, const char* param2, const char* param3, const char* param4)
{
	_InternalFormatStringMessage(outString, sString, param1, param2, param3, param4);
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FormatStringMessage(wstring& outString, const wstring& sString, const wchar_t** sParams, int nParams)
{
	_InternalFormatStringMessage(outString, sString, sParams, nParams);
}

//////////////////////////////////////////////////////////////////////////
void CLocalizedStringsManager::FormatStringMessage(wstring& outString, const wstring& sString, const wchar_t* param1, const wchar_t* param2, const wchar_t* param3, const wchar_t* param4)
{
	_InternalFormatStringMessage(outString, sString, param1, param2, param3, param4);
}

//////////////////////////////////////////////////////////////////////////
int CLocalizedStringsManager::GetMemoryUsage( ICrySizer *pSizer )
{	
	pSizer->AddObject( this,sizeof(*this) );
	pSizer->AddObject(m_languages);	
	pSizer->AddObject(m_prototypeEvents);	
	pSizer->AddObject(m_tempWString);
	pSizer->AddObject(m_characterNameSet);
	pSizer->AddObject(m_pLanguage);
	
	return 0;
}

#if defined (WIN32) || defined(WIN64)
namespace
{
	struct LanguageID 
	{
		const char*   language;
		unsigned long lcID;
	};

	LanguageID languageIDArray[] =
	{
		{ "ChineseT",  0x404 }, // 1028
		{ "Czech",     0x405 }, // 1029
		{ "English",   0x409 }, // 1033
		{ "French",    0x40C }, // 1036
		{ "German",    0x407 }, // 1031
		{ "Hungarian", 0x40E }, // 1038
		{ "Italian",   0x410 }, // 1040
		{ "Japanese",  0x411 }, // 1041
		{ "Korean",    0x412 }, // 1042
		{ "Polish",    0x415 }, // 1045
		{ "Russian",   0x419 }, // 1049
		{ "Spanish",   0x40A }, // 1034
		{ "Thai",      0x41E }, // 1054
		{ "Turkish",   0x41F }, // 1055
	};

	const size_t numLanguagesIDs = sizeof(languageIDArray) / sizeof(languageIDArray[0]);

	LanguageID GetLanguageID(const char* language)
	{
		// default is English (US)
		const LanguageID defaultLanguage = { "English", 0x409 };
		for (int i = 0; i<numLanguagesIDs; ++i)
		{
			if (stricmp(language, languageIDArray[i].language) == 0)
				return languageIDArray[i];
		}
		return defaultLanguage;
	}

	LanguageID g_currentLanguageID = { 0, 0 };
};
#endif

void CLocalizedStringsManager::InternalSetCurrentLanguage(CLocalizedStringsManager::SLanguage *pLanguage)
{
	m_pLanguage = pLanguage;
#if defined (WIN32) || defined(WIN64)
	if (m_pLanguage != 0)
		g_currentLanguageID = GetLanguageID(m_pLanguage->sLanguage);
	else
	{
		g_currentLanguageID.language = 0;
		g_currentLanguageID.lcID = 0;
	}
#endif
	// TODO: on Linux based systems we should now set the locale
	// Enabling this on windows something seems to get corrupted...
	// on Windows we always use Windows Platform Functions, which use the lcid










}

void CLocalizedStringsManager::LocalizeDuration(int seconds, wstring &outDurationString)
{
	int s = seconds;
	int d,h,m;
	d  = s / 86400;
	s -= d * 86400;
	h = s / 3600;
	s -= h * 3600;
	m = s / 60;
	s = s - m * 60;
	string str;
	if (d > 1)
		str.Format("%d @ui_days %02d:%02d:%02d", d, h, m, s);
	else if (d > 0)
		str.Format("%d @ui_day %02d:%02d:%02d", d, h, m, s);
	else if (h > 0)
		str.Format("%02d:%02d:%02d", h, m, s);
	else
		str.Format("%02d:%02d", m, s);
	LocalizeString(str, outDurationString);
}

#if defined (WIN32) || defined(WIN64)
namespace 
{
	void UnixTimeToFileTime(time_t unixtime, FILETIME* filetime)
	{ 
		LONGLONG longlong = Int32x32To64(unixtime, 10000000) + 116444736000000000; 
		filetime->dwLowDateTime = (DWORD) longlong;
		filetime->dwHighDateTime = (DWORD)(longlong >> 32);
	}

	void UnixTimeToSystemTime(time_t unixtime, SYSTEMTIME* systemtime)
	{ 
		FILETIME filetime;
		UnixTimeToFileTime(unixtime, &filetime);
		FileTimeToSystemTime(&filetime, systemtime); 
	} 

	time_t UnixTimeFromFileTime(const FILETIME* filetime)
	{
		LONGLONG longlong = filetime->dwHighDateTime;
		longlong <<= 32; longlong |= filetime->dwLowDateTime;
		longlong -= 116444736000000000; 
		return longlong / 10000000;
	}

	time_t UnixTimeFromSystemTime(const SYSTEMTIME* systemtime)
	{ 
		// convert systemtime to filetime
		FILETIME filetime; 
		SystemTimeToFileTime(systemtime, &filetime);
		// convert filetime to unixtime
		time_t unixtime = UnixTimeFromFileTime(&filetime);
		return unixtime;
	}
};

wchar_t CLocalizedStringsManager::ToUpperCase(wchar_t c)
{
	wchar_t widechar;
	LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
	/* convert wide char to uppercase */
	if ( 0 == ::LCMapStringW(lcID, LCMAP_UPPERCASE,	(LPCWSTR)&c, 1,	(LPWSTR)&widechar, 1))
	{
		return c;
	}
	return widechar;
}

wchar_t CLocalizedStringsManager::ToLowerCase(wchar_t c)
{
	wchar_t widechar;
	LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
	/* convert wide char to lowercase */
	if ( 0 == ::LCMapStringW(lcID, LCMAP_LOWERCASE,	(LPCWSTR)&c, 1,	(LPWSTR)&widechar, 1))
	{
		return c;
	}
	return widechar;
}

void CLocalizedStringsManager::LocalizeTime(time_t t, bool bMakeLocalTime, bool bShowSeconds, wstring& outTimeString)
{
	if (bMakeLocalTime)
	{
		struct tm thetime;
		thetime = *localtime(&t);
		t = gEnv->pTimer->DateToSecondsUTC(thetime);
	}
	outTimeString.resize(0);
	LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
	DWORD flags = bShowSeconds == false ? TIME_NOSECONDS : 0;
	SYSTEMTIME systemTime;
	UnixTimeToSystemTime(t, &systemTime);
	int len = ::GetTimeFormatW(lcID, flags, &systemTime, 0, 0, 0);
	if (len > 0)
	{
		// len includes terminating null!
		CryFixedWStringT<256> tmpString;
		tmpString.resize(len);
		::GetTimeFormatW(lcID, flags, &systemTime, 0, (wchar_t*) tmpString.c_str(), len);
		outTimeString.assign(tmpString.c_str(), len-1);
	}
}

void CLocalizedStringsManager::LocalizeDate(time_t t, bool bMakeLocalTime, bool bShort, bool bIncludeWeekday, wstring& outDateString)
{
	if (bMakeLocalTime)
	{
		struct tm thetime;
		thetime = *localtime(&t);
		t = gEnv->pTimer->DateToSecondsUTC(thetime);
	}
	outDateString.resize(0);
	LCID lcID = g_currentLanguageID.lcID ? g_currentLanguageID.lcID : LOCALE_USER_DEFAULT;
	SYSTEMTIME systemTime;
	UnixTimeToSystemTime(t, &systemTime);

	// len includes terminating null!
	CryFixedWStringT<256> tmpString;

	if (bIncludeWeekday)
	{
		// Get name of day
		int len = ::GetDateFormatW(lcID, 0, &systemTime, L"ddd", 0, 0);
		if (len > 0)
		{
			// len includes terminating null!
			tmpString.resize(len);
			::GetDateFormatW(lcID, 0, &systemTime, L"ddd", (wchar_t*) tmpString.c_str(), len);
			outDateString.append(tmpString.c_str(), len-1);
			outDateString.append(L" ");
		}
	}
	DWORD flags = bShort ? DATE_SHORTDATE : DATE_LONGDATE;
	int len = ::GetDateFormatW(lcID, flags, &systemTime, 0, 0, 0);
	if (len > 0)
	{
		// len includes terminating null!
		tmpString.resize(len);
		::GetDateFormatW(lcID, flags, &systemTime, 0, (wchar_t*) tmpString.c_str(), len);
		outDateString.append(tmpString.c_str(), len-1);
	}
}

//////////////////////////////////////////////////////////////////////////
// zlib defines WIN32 so must be last in file.
//////////////////////////////////////////////////////////////////////////
#include "zlib/zlib.h"
//////////////////////////////////////////////////////////////////////////
int CLocalizedStringsManager::CompressWChar( void *dest,unsigned int &destLen,const void *source, unsigned int sourceLen,int level )
{
	unsigned long dlen = destLen;
	int res = ::compress2( (Bytef*)dest, &dlen, (const Bytef*)source, sourceLen, Z_BEST_COMPRESSION );
	destLen = dlen;
	return res;
}

//////////////////////////////////////////////////////////////////////////
int CLocalizedStringsManager::UncompressWChar( void *dest,unsigned int &destLen,const void *source, unsigned int sourceLen )
{
	unsigned long dlen = destLen;
	int res = ::uncompress( (Bytef*)dest,&dlen,(const Bytef*)source,sourceLen );
	destLen = dlen;
	return res;
}

#else // #if defined (WIN32) || defined(WIN64)

// on non windows system we rely on setlocale and CRT functions
wchar_t CLocalizedStringsManager::ToUpperCase(wchar_t c)
{
	return towupper(c);
}

wchar_t CLocalizedStringsManager::ToLowerCase(wchar_t c)
{
	return towlower(c);
}

void CLocalizedStringsManager::LocalizeTime(time_t t, bool bMakeLocalTime, bool bShowSeconds, wstring& outTimeString)
{
	struct tm theTime;
	if (bMakeLocalTime)
		theTime = *localtime(&t);
	else
		theTime = *gmtime(&t);

	wchar_t buf[256];
	const size_t bufSize = sizeof(buf) / sizeof(buf[0]);
	wcsftime(buf, bufSize, bShowSeconds ? L"%#X" :L"%X", &theTime);
	buf[bufSize-1] = 0;
	outTimeString.assign(buf);
}

void CLocalizedStringsManager::LocalizeDate(time_t t, bool bMakeLocalTime, bool bShort, bool bIncludeWeekday, wstring& outDateString)
{
	struct tm theTime;
	if (bMakeLocalTime)
		theTime = *localtime(&t);
	else
		theTime = *gmtime(&t);

	wchar_t buf[256];
	const size_t bufSize = sizeof(buf) / sizeof(buf[0]);
	const wchar_t* format = bShort ? (bIncludeWeekday ? L"%a %x" : L"%x") : L"%#x"; // long format always contains Weekday name
	wcsftime(buf, bufSize, format, &theTime);
	buf[bufSize-1] = 0;
	outDateString.assign(buf);
}



#endif

#include UNIQUE_VIRTUAL_WRAPPER(ILocalizationManager)
